home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 26 / Cream of the Crop 26.iso / program / smix130.zip / SMIX.PAS < prev    next >
Pascal/Delphi Source File  |  1997-06-06  |  39KB  |  1,101 lines

  1. {       SMIX is Copyright 1995 by Ethan Brodsky.  All rights reserved.       }
  2.  
  3. unit SMix; {Version 1.30}
  4.  {$X+} {$G+} {$R-}
  5.   interface
  6.     const
  7.       BlockLength   = 512;      {Size of digitized sound block               }
  8.       LoadChunkSize = 2048;     {Chunk size used for loading sounds from disk}
  9.       Voices        = 8;        {Number of available voices                  }
  10.       DefSamplingRate = 22025;  {Sampling rate for output                    }
  11.     type
  12.       PSound = ^TSound;
  13.       TSound =
  14.         record
  15.           XMSHandle: word;
  16.           StartOfs:  LongInt;
  17.           SoundSize: LongInt;
  18.         end;
  19.     function InitSB(BaseIO: word; IRQ: byte; DMA, DMA16: byte): boolean;
  20.       {Initializes control parameters, resets DSP, and installs int. handler }
  21.       { Parameters: (Can be found using GetSettings procedure in Detect)     }
  22.       {  BaseIO:   Sound card base IO address                                }
  23.       {  IRQ:      Sound card IRQ setting                                    }
  24.       {  DMA:      Sound card 8-bit DMA channel                              }
  25.       {  DMA16:    Sound card 16-bit DMA channel (0 if not supported)        }
  26.       { Returns:                                                             }
  27.       {  TRUE:     Sound card successfully initialized (Maybe)               }
  28.       {  FALSE:    Sound card could not be initialized                       }
  29.     procedure ShutdownSB;
  30.       {Removes interrupt handler and resets DSP                              }
  31.  
  32.     procedure SetSamplingRate(Rate: Word);
  33.       {Overrides default sampling rate set with DefSamplingRate constant     }
  34.       { Parameters:                                                          }
  35.       {  Rate:     New sampling rate (will be rounded by sound card)         }
  36.       {This function can either be called before calling InitMixing (after   }
  37.       {calling InitSB) to change the sampling rate before playback begins or }
  38.       {called during playback to change the rate dynamically.  The lowest    }
  39.       {sampling rate that will work is roughly 5000 HZ.  The highest sampling}
  40.       {rate that will work on all sound cards is 22050 HZ.  If you only want }
  41.       {to support the SB16, then you can use rates all the way up to 48000 HZ}
  42.  
  43.     procedure InitMixing;
  44.       {Allocates internal buffers and starts digitized sound output          }
  45.     procedure ShutdownMixing;
  46.       {Deallocates internal buffers and stops digitized sound output         }
  47.  
  48.     function  InitXMS: boolean;
  49.       {Attempts to intialize extended memory                                 }
  50.       { Returns:                                                             }
  51.       {  TRUE:     Extended memory successfully initialized                  }
  52.       {  FALSE:    Extended memory could not be initialized                  }
  53.     function  GetFreeXMS: word;
  54.       {Returns amount of free XMS memory (In kilobytes)                      }
  55.  
  56.     procedure InitSharing;
  57.       {Allocates an EMB that all sounds are stored in.  This preserves EMB   }
  58.       {handles, which are a scarce resource.  Call this on initialization and}
  59.       {all sounds will automatically be stored in one EMB.  Call LoadSound as}
  60.       {usual to allocate a sound, but FreeSound only deallocates the sound   }
  61.       {data structure.  Call ShutdownSharing before program termination to   }
  62.       {free allocated extended memory.                                       }
  63.     procedure ShutdownSharing;
  64.       {Shuts down EMB sharing and frees all allocated extended memory        }
  65.  
  66.     function OpenSoundResourceFile(FileName: string): boolean;
  67.       {Call this to open a resource file for loading sounds.  After this has }
  68.       {been called, the Key parameter in the LoadSound function is used as a }
  69.       {resource key to locate the sound data in this file.                   }
  70.       { Parameters:                                                          }
  71.       {  FileName: File name of resource file                                }
  72.       { Returns:                                                             }
  73.       {  TRUE:     Sound resource file exists                                }
  74.       {  FALSE:    Error, sound resource file does not exist                 }
  75.     procedure CloseSoundResourceFile;
  76.       {Close sound resource file.  If you have called this, the Key parameter}
  77.       {will act as a filename instead of a resource key.                     }
  78.  
  79.     function LoadSound(var Sound: PSound; Key: string): boolean;
  80.       {Allocates an extended memory block and loads a sound from a file      }
  81.       { Parameters:                                                          }
  82.       {  Sound:    Unallocated pointer to sound data structure               }
  83.       {  Key:      If a resource file has been opened then key is a resource }
  84.       {            identifier.  Use the same ID as you used for SNDLIB.      }
  85.       {            If a resource file has not been opened, then key is the   }
  86.       {            filename to load the sound data from.                     }
  87.       { Returns:                                                             }
  88.       {  TRUE:     Sound loaded sucessfully                                  }
  89.       {  FALSE:    Error loading sound                                       }
  90.     procedure FreeSound(var Sound: PSound);
  91.       {Deallocates extended memory and destroys sound data structure         }
  92.       { Parameters:                                                          }
  93.       {  Sound:    Unallocated pointer to sound data structure               }
  94.  
  95.     function StartSound(Sound: PSound; Index: byte; Loop: boolean): boolean;
  96.       {Starts playing a sound                                                }
  97.       { Parameters:                                                          }
  98.       {  Sound:    Pointer to sound data structure                           }
  99.       {  Index:    A number to keep track of the sound with (Used to stop it)}
  100.       {  Loop:     Indicates whether the sound should be continuously looped }
  101.       { Returns:                                                             }
  102.       {  TRUE:     Sound was played                                          }
  103.       {  FALSE:    There were no free channels and the sound was not played  }
  104.     procedure StopSound(Index: byte);
  105.       {Stops playing sound                                                   }
  106.       { Parameters:                                                          }
  107.       {  Index:    Index of sound to stop (All with given index are stopped) }
  108.     function SoundPlaying(Index: byte): boolean;
  109.       {Checks if a sound is still playing                                    }
  110.       { Parameters:                                                          }
  111.       {  Index:    Index used when the sound was started                     }
  112.       { Returns:                                                             }
  113.       {  TRUE      At least oen sound with the specified index is playing    }
  114.       {  FALSE     No sounds with the specified index are playing            }
  115.  
  116.     var
  117.       IntCount   : LongInt;  {Number of sound interrupts that have occured   }
  118.       DSPVersion : word;     {Contains the version of the installed DSP chip }
  119.       AutoInit   : boolean;  {Tells Auto-initialized DMA transfers are in use}
  120.       SixteenBit : boolean;  {Tells whether 16-bit sound output is occuring  }
  121.       VoiceCount : byte;     {Number of voices currently in use              }
  122.       SMIXsound  : boolean;  {Tells whether SMIX is currently operating      }
  123.  
  124.   implementation
  125.     uses
  126.       CRT,
  127.       DOS,
  128.       XMS;
  129.     const
  130.       BufferLength = BlockLength * 2;
  131.     var
  132.       ResetPort        : word;
  133.       ReadPort         : word;
  134.       WritePort        : word;
  135.       PollPort         : word;
  136.       AckPort          : word;
  137.  
  138.       PICRotatePort    : word;
  139.       PICMaskPort      : word;
  140.  
  141.       DMAMaskPort      : word;
  142.       DMAClrPtrPort    : word;
  143.       DMAModePort      : word;
  144.       DMABaseAddrPort  : word;
  145.       DMACountPort     : word;
  146.       DMAPagePort      : word;
  147.  
  148.       IRQStartMask     : byte;
  149.       IRQStopMask      : byte;
  150.       IRQIntVector     : byte;
  151.  
  152.       DMAStartMask     : byte;
  153.       DMAStopMask      : byte;
  154.       DMAMode          : byte;
  155.       DMALength        : word;
  156.  
  157.       SMIXinitialized  : boolean;
  158.  
  159.       OldIntVector     : pointer;
  160.       OldExitProc      : pointer;
  161.  
  162.       HandlerInstalled : boolean;
  163.  
  164.       SamplingRate     : word;
  165.  
  166.     procedure WriteDSP(Value: byte);
  167.       begin
  168.         repeat until (Port[WritePort] and $80) = 0;
  169.         Port[WritePort] := Value;
  170.       end;
  171.  
  172.     function ReadDSP: byte;
  173.       begin
  174.         repeat until (Port[PollPort] and $80) <> 0;
  175.         ReadDSP := Port[ReadPort];
  176.       end;
  177.  
  178.     function ResetDSP: boolean;
  179.       var
  180.         i: byte;
  181.       begin
  182.         Port[ResetPort] := 1;
  183.         Delay(1);                              {One millisecond}
  184.         Port[ResetPort] := 0;
  185.         i := 100;
  186.         while (ReadDSP <> $AA) and (i > 0) do Dec(i);
  187.         if i > 0
  188.           then ResetDSP := true
  189.           else ResetDSP := false;
  190.       end;
  191.  
  192.     procedure InstallHandler; forward;
  193.     procedure UninstallHandler; forward;
  194.  
  195.     procedure MixExitProc; far; forward;
  196.  
  197.     function InitSB(BaseIO: word; IRQ: byte; DMA, DMA16: byte): boolean;
  198.       begin
  199.        {Sound card IO ports}
  200.         ResetPort  := BaseIO + $6;
  201.         ReadPort   := BaseIO + $A;
  202.         WritePort  := BaseIO + $C;
  203.         PollPort   := BaseIO + $E;
  204.  
  205.        {Reset DSP, get version, and pick output mode}
  206.         if not(ResetDSP)
  207.           then
  208.             begin
  209.               InitSB := false;
  210.               Exit;
  211.             end;
  212.         WriteDSP($E1);  {Get DSP version number}
  213.         DSPVersion := ReadDSP shl 8;  DSPVersion := DSPVersion + ReadDSP;
  214.         AutoInit   := DSPVersion >= $0200;
  215.         SixteenBit := (DSPVersion >= $0400) and (DMA16 <> $FF) and (DMA16 > 3);
  216.  
  217.        {Compute interrupt ports and parameters}
  218.         if IRQ <= 7
  219.           then
  220.             begin
  221.               IRQIntVector  := $08+IRQ;
  222.               PICMaskPort   := $21;
  223.             end
  224.           else
  225.             begin
  226.               IRQIntVector  := $70+IRQ-8;
  227.               PICMaskPort   := $A1;
  228.             end;
  229.         IRQStopMask  := 1 shl (IRQ mod 8);
  230.         IRQStartMask := not(IRQStopMask);
  231.  
  232.        {Compute DMA ports and parameters}
  233.         if SixteenBit
  234.           then {Sixteen bit}
  235.             begin
  236.               DMAMaskPort     := $D4;
  237.               DMAClrPtrPort   := $D8;
  238.               DMAModePort     := $D6;
  239.               DMABaseAddrPort := $C0 + 4*(DMA16-4);
  240.               DMACountPort    := $C2 + 4*(DMA16-4);
  241.               case DMA16
  242.                 of
  243.                   5:  DMAPagePort := $8B;
  244.                   6:  DMAPagePort := $89;
  245.                   7:  DMAPagePort := $8A;
  246.                 end;
  247.               DMAStopMask  := DMA16-4 + $04;   {000001xx}
  248.               DMAStartMask := DMA16-4 + $00;   {000000xx}
  249.               DMAMode      := DMA16-4 + $58;   {010110xx}
  250.               AckPort := BaseIO + $F;
  251.             end
  252.           else {Eight bit}
  253.             begin
  254.               DMAMaskPort     := $0A;
  255.               DMAClrPtrPort   := $0C;
  256.               DMAModePort     := $0B;
  257.               DMABaseAddrPort := $00 + 2*DMA;
  258.               DMACountPort    := $01 + 2*DMA;
  259.               case DMA
  260.                 of
  261.                   0:  DMAPagePort := $87;
  262.                   1:  DMAPagePort := $83;
  263.                   2:  DMAPagePort := $81;
  264.                   3:  DMAPagePort := $82;
  265.                 end;
  266.               DMAStopMask  := DMA + $04;       {000001xx}
  267.               DMAStartMask := DMA + $00;       {000000xx}
  268.               if AutoInit
  269.                 then DMAMode := DMA + $58      {010110xx}
  270.                 else DMAMode := DMA + $48;     {010010xx}
  271.               AckPort := BaseIO + $E;
  272.             end;
  273.           if AutoInit
  274.             then DMALength := BufferLength
  275.             else DMALength := BlockLength;
  276.           InstallHandler;
  277.  
  278.           SMIXinitialized := true;
  279.           SMIXsound       := false;
  280.           SamplingRate    := DefSamplingRate;
  281.  
  282.           InitSB := true;
  283.       end;
  284.  
  285.     procedure ShutdownSB;
  286.       begin
  287.         if HandlerInstalled
  288.           then UninstallHandler;
  289.         ResetDSP;
  290.       end;
  291.  
  292.     function InitXMS: boolean;
  293.       begin
  294.         InitXMS := true;
  295.         if not(XMSInstalled)
  296.           then InitXMS := false
  297.           else XMSInit;
  298.       end;
  299.     function GetFreeXMS: word;
  300.       begin
  301.         GetFreeXMS := XMSGetFreeMem;
  302.       end;
  303.  
  304.    {Voice control}
  305.     type
  306.       PVoice = ^TVoice;
  307.       TVoice =
  308.         record
  309.           Sound:     PSound;
  310.           Index:     byte;
  311.           CurPos:    LongInt;
  312.           Loop:      boolean;
  313.         end;
  314.     var
  315.       VoiceInUse: array[0..Voices-1] of boolean;
  316.       Voice:      array[0..Voices-1] of TVoice;
  317.       CurBlock:   byte;
  318.    {Sound buffer}
  319.     var
  320.       SoundBlock: array[1..BlockLength+1] of ShortInt;
  321.         {The length of XMS copies under HIMEM.SYS must be a mutiple  }
  322.         {of two.  If the sound data ends in mid-block, it may not be }
  323.         {possible to round up without corrupting memory.  Therefore, }
  324.         {the copy buffer has been extended by one byte to eliminate  }
  325.         {this problem.                                               }
  326.  
  327.    {Mixing buffers}
  328.     type
  329.       PMixingBlock = ^TMixingBlock;
  330.       TMixingBlock = array[1..BlockLength] of integer;
  331.     var
  332.       MixingBlock  : TMixingBlock;
  333.  
  334.    {Output buffers}
  335.     type {8-bit}
  336.       POut8Block   = ^TOut8Block;
  337.       TOut8Block   = array[1..BlockLength] of byte;
  338.       POut8Buffer  = ^TOut8Buffer;
  339.       TOut8Buffer  = array[1..2] of TOut8Block;
  340.     type {16-bit}
  341.       POut16Block  = ^TOut16Block;
  342.       TOut16Block  = array[1..BlockLength] of integer;
  343.       POut16Buffer = ^TOut16Buffer;
  344.       TOut16Buffer = array[1..2] of TOut16Block;
  345.     var
  346.       OutMemArea  : pointer;
  347.       Out8Buffer  : POut8Buffer;
  348.       Out16Buffer : POut16Buffer;
  349.     var
  350.       BlockPtr    : array[1..2] of pointer;
  351.       CurBlockPtr : pointer;
  352.     var
  353.      {For auto-initialized transfers (Whole buffer)}
  354.       BufferAddr : LongInt;
  355.       BufferPage : byte;
  356.       BufferOfs  : word;
  357.      {For single-cycle transfers (One block at a time)}
  358.       BlockAddr  : array[1..2] of LongInt;
  359.       BlockPage  : array[1..2] of byte;
  360.       BlockOfs   : array[1..2] of word;
  361.  
  362.    {Clipping for 8-bit output}
  363.     var
  364.        Clip8 : array[-128*Voices..128*Voices] of byte;
  365.  
  366.     function TimeConstant(Rate: word): byte;
  367.       begin
  368.         TimeConstant := 256 - (1000000 div Rate);
  369.       end;
  370.  
  371.     procedure InitSamplingRate(Rate: word);
  372.       begin
  373.         if SixteenBit
  374.           then
  375.             begin
  376.                 WriteDSP($41);      {Set digitized sound output sampling rate}
  377.                 WriteDSP(Hi(Rate));
  378.                 WriteDSP(Lo(Rate));
  379.             end
  380.           else
  381.             begin
  382.                 WriteDSP($40);      {Set digitized sound time constant       }
  383.                 WriteDSP(TimeConstant(Rate));
  384.             end;
  385.       end;
  386.  
  387.     procedure SetSamplingRate(Rate: word);
  388.       begin
  389.         SamplingRate := Rate;
  390.  
  391.         if (SMIXsound)
  392.           then
  393.             begin
  394.               if SixteenBit
  395.                 then
  396.                   begin
  397.                     InitSamplingRate(SamplingRate);
  398.                     WriteDSP($D6); {Continue 16-bit DMA mode digitized sound }
  399.                   end
  400.                 else
  401.                   begin
  402.                     WriteDSP($D0); {Pause 8-bit DMA mode digitized sound     }
  403.                     InitSamplingRate(SamplingRate);
  404.                     WriteDSP($D4); {Continue 8-bit DMA mode digitized sound  }
  405.                   end;
  406.             end;
  407.       end;
  408.  
  409.     procedure StartDAC;
  410.       begin
  411.         Port[DMAMaskPort]     := DMAStopMask;
  412.         Port[DMAClrPtrPort]   := $00;
  413.         Port[DMAModePort]     := DMAMode;
  414.         Port[DMABaseAddrPort] := Lo(BufferOfs);
  415.         Port[DMABaseAddrPort] := Hi(BufferOfs);
  416.         Port[DMACountPort]    := Lo(DMALength-1);
  417.         Port[DMACountPort]    := Hi(DMALength-1);
  418.         Port[DMAPagePort]     := BufferPage;
  419.         Port[DMAMaskPort]     := DMAStartMask;
  420.  
  421.         InitSamplingRate(SamplingRate);
  422.  
  423.         if SixteenBit
  424.           then {Sixteen bit: SB16 and up (DSP 4.xx)}
  425.             begin
  426.               WriteDSP($B6);        {16-bit DMA command: D/A, Auto-Init, FIFO}
  427.               WriteDSP($10);        {16-bit DMA mode:    Signed Mono         }
  428.               WriteDSP(Lo(BlockLength - 1));
  429.               WriteDSP(Hi(BlockLength - 1));
  430.             end
  431.           else {Eight bit}
  432.             begin
  433.               WriteDSP($D1);        {Turn on speaker                         }
  434.               if AutoInit
  435.                 then {Eight bit auto-initialized: SBPro and up (DSP 2.00+)}
  436.                   begin
  437.                     WriteDSP($48);  {Set DSP block transfer size             }
  438.                     WriteDSP(Lo(BlockLength - 1));
  439.                     WriteDSP(Hi(BlockLength - 1));
  440.                     WriteDSP($1C);  {8-bit auto-init DMA mono sound output   }
  441.                   end
  442.                 else {Eight bit single-cycle: Sound Blaster (DSP 1.xx+)}
  443.                   begin
  444.                     WriteDSP($14);  {8-bit single-cycle DMA sound output     }
  445.                     WriteDSP(Lo(BlockLength - 1));
  446.                     WriteDSP(Hi(BlockLength - 1));
  447.                   end;
  448.             end;
  449.  
  450.           SMIXsound := true;
  451.       end;
  452.  
  453.     procedure StopDAC;
  454.       begin
  455.         SMIXsound := false;
  456.  
  457.         if SixteenBit
  458.           then {Sixteen bit}
  459.             begin
  460.               WriteDSP($D5);        {Pause 16-bit DMA sound I/O              }
  461.             end
  462.           else {Eight bit}
  463.             begin
  464.               WriteDSP($D0);        {Pause 8-bit DMA mode sound I/O          }
  465.               WriteDSP($D3);        {Turn off speaker                        }
  466.             end;
  467.         Port[DMAMaskPort] := DMAStopMask;
  468.       end;
  469.  
  470.    {Setup for storing all sounds in one extended memory block (Saves handles)}
  471.     var
  472.       SharedEMB    : boolean;
  473.       SharedHandle : word;
  474.       SharedSize   : LongInt;
  475.     procedure InitSharing;
  476.       begin
  477.         SharedEMB  := true;
  478.         SharedSize := 0;
  479.         XMSAllocate(SharedHandle, SharedSize);
  480.       end;
  481.     procedure ShutdownSharing;
  482.       begin
  483.         if SharedEMB then XMSFree(SharedHandle);
  484.         SharedEMB := false;
  485.       end;
  486.  
  487.    {Setup for sound resource files}
  488.     var
  489.       ResourceFile     : boolean;
  490.       ResourceFilename : string;
  491.  
  492.     function FExist(FileName: string): boolean;
  493.       var
  494.         f: file;
  495.       begin
  496.         Assign(f, FileName);
  497. {$I-}
  498.         Reset(f);
  499. {$I+}
  500.         if (IOResult = 0)
  501.           then FExist := true
  502.           else FExist := false;
  503.       end;
  504.  
  505.     function OpenSoundResourceFile(FileName: string): boolean;
  506.       begin
  507.         ResourceFile     := true;
  508.         ResourceFilename := FileName;
  509.  
  510.         OpenSoundResourceFile := FExist(FileName);
  511.       end;
  512.  
  513.     procedure CloseSoundResourceFile;
  514.       begin
  515.         ResourceFile     := false;
  516.         ResourceFilename := '';
  517.       end;
  518.  
  519.     type
  520.       TKey = array[1..8] of char;
  521.  
  522.     var
  523.       SoundFile : file;
  524.       SoundSize : LongInt;
  525.  
  526.     function MatchingKeys(a, b: TKey): boolean;
  527.       var
  528.         i: integer;
  529.       begin
  530.         MatchingKeys := true;
  531.  
  532.         for i := 1 to 8 do
  533.           if a <> b
  534.             then
  535.               MatchingKeys := false;
  536.       end;
  537.  
  538.     procedure GetSoundFile(Key: string);
  539.       type
  540.         Resource =
  541.           record
  542.             Key:   TKey;
  543.             Start: LongInt;
  544.             Size:  LongInt;
  545.           end;
  546.       var
  547.         NumSounds: integer;
  548.         ResKey:    TKey;
  549.         ResHeader: Resource;
  550.         Index:     integer;
  551.         i:         integer;
  552.         Found:     boolean;
  553.       begin
  554.         if ResourceFile
  555.           then
  556.             begin
  557.               for i := 1 to 8 do
  558.                 if i <= Length(Key)
  559.                   then ResKey[i] := Key[i]
  560.                   else ResKey[i] := #0;
  561.  
  562.               Assign(SoundFile, ResourceFilename);  Reset(SoundFile, 1);
  563.               BlockRead(SoundFile, NumSounds, SizeOf(NumSounds));
  564.  
  565.               Found := false;
  566.               Index := 0;
  567.  
  568.               while not(Found) and (Index < NumSounds) do
  569.                 begin
  570.                   Index := Index + 1;
  571.                   BlockRead(SoundFile, ResHeader, SizeOf(ResHeader));
  572.  
  573.                   if MatchingKeys(ResHeader.Key, ResKey)
  574.                     then
  575.                       Found := true;
  576.                 end;
  577.  
  578.               if Found
  579.                 then
  580.                   begin
  581.                     Seek(SoundFile, ResHeader.Start);
  582.                     SoundSize := ResHeader.Size;
  583.                   end
  584.                 else
  585.                   SoundSize := 0;
  586.             end
  587.           else
  588.             begin
  589.               Assign(SoundFile, Key);  Reset(SoundFile, 1);
  590.               SoundSize := FileSize(SoundFile);
  591.             end;
  592.       end;
  593.  
  594.     function Min(a, b: LongInt): LongInt;
  595.       begin
  596.         if a < b
  597.           then Min := a
  598.           else Min := b;
  599.       end;
  600.  
  601.    {Loading and freeing sounds}
  602.     function LoadSound(var Sound: PSound; Key: string): boolean;
  603.       var
  604.         Size: LongInt;
  605.         InBuffer: array[1..LoadChunkSize] of byte;
  606.         Remaining: LongInt;
  607.         MoveParams: TMoveParams;
  608.       begin
  609.         LoadSound := false;
  610.  
  611.         GetSoundFile(Key);
  612.  
  613.         if (SoundSize = 0)
  614.           then exit;
  615.  
  616.         New(Sound);
  617.         Sound^.SoundSize := SoundSize;
  618.  
  619.         if not(SharedEMB)
  620.           then
  621.             begin
  622.               Sound^.StartOfs := 0;
  623.               if not(XMSAllocate(Sound^.XMSHandle, (SoundSize + 1023) div 1024))
  624.                 then exit;
  625.             end
  626.           else
  627.             begin
  628.               Sound^.StartOfs := SharedSize;
  629.               Sound^.XMSHandle := SharedHandle;
  630.               SharedSize := SharedSize + SoundSize;
  631.               if not(XMSReallocate(SharedHandle, (SharedSize + 1023) div 1024))
  632.                 then exit;
  633.             end;
  634.         MoveParams.SourceHandle := 0;
  635.         MoveParams.SourceOffset := LongInt(Addr(InBuffer));
  636.         MoveParams.DestHandle   := Sound^.XMSHandle;
  637.         MoveParams.DestOffset   := Sound^.StartOfs;
  638.  
  639.         Remaining := Sound^.SoundSize;
  640.  
  641.         repeat
  642.           MoveParams.Length := Min(Remaining, LoadChunkSize);
  643.           BlockRead(SoundFile, InBuffer, MoveParams.Length);
  644.           MoveParams.Length := ((MoveParams.Length+1) div 2) * 2;
  645.             {XMS copy lengths must be a multiple of two}
  646.           XMSMove(@MoveParams);
  647.           Inc(MoveParams.DestOffset, MoveParams.Length);
  648.           Dec(Remaining, MoveParams.Length);
  649.         until not(Remaining > 0);
  650.  
  651.         Close(SoundFile);
  652.  
  653.         LoadSound := true;
  654.       end;
  655.  
  656.     procedure FreeSound(var Sound: PSound);
  657.       begin
  658.         if not(SharedEMB) then XMSFree(Sound^.XMSHandle);
  659.         Dispose(Sound); Sound := nil;
  660.       end;
  661.  
  662.    {Voice maintainance}
  663.     procedure DeallocateVoice(VoiceNum: byte);
  664.       begin
  665.         VoiceInUse[VoiceNum] := false;
  666.         with Voice[VoiceNum] do
  667.           begin
  668.             Sound    := nil;
  669.             Index    := 0;
  670.             CurPos   := 0;
  671.             Loop     := false;
  672.           end;
  673.       end;
  674.  
  675.     function StartSound(Sound: PSound; Index: byte; Loop: boolean): boolean;
  676.       var
  677.         i, Slot: byte;
  678.       begin
  679.         StartSound := false;      {assume that we cannot play the sound}
  680.  
  681.         Slot := $FF; i := 0;
  682.         repeat
  683.           if not(VoiceInUse[i])
  684.             then Slot := i;
  685.           Inc(i);
  686.         until ((Slot <> $FF) or (i=Voices));
  687.         if Slot <> $FF
  688.           then
  689.             begin
  690.               Inc(VoiceCount);
  691.               Voice[Slot].Sound    := Sound;
  692.               Voice[Slot].Index    := Index;
  693.               Voice[Slot].CurPos   := 0;
  694.               Voice[Slot].Loop     := Loop;
  695.  
  696.               VoiceInUse[Slot] := true;
  697.  
  698.               StartSound := true; {success}
  699.             end;
  700.       end;
  701.  
  702.     procedure StopSound(Index: byte);
  703.       var
  704.         i: byte;
  705.       begin
  706.         for i := 0 to Voices-1 do
  707.           if (Voice[i].Sound <> nil) and (Voice[i].Index = Index)
  708.             then
  709.               begin
  710.                 DeallocateVoice(i);
  711.                 Dec(VoiceCount);
  712.               end;
  713.       end;
  714.  
  715.     function SoundPlaying(Index: byte): boolean;
  716.       var
  717.         i: byte;
  718.       begin
  719.         SoundPlaying := False;
  720.  
  721.         for i := 0 to Voices-1 do
  722.           if (Voice[i].Sound <> nil) and (Voice[i].Index = Index)
  723.             then SoundPlaying := True;
  724.       end;
  725.  
  726.     procedure UpdateVoices;
  727.       var
  728.         VoiceNum: byte;
  729.       begin
  730.         for VoiceNum := 0 to Voices-1 do
  731.           begin
  732.             if VoiceInUse[VoiceNum]
  733.               then
  734.                 if Voice[VoiceNum].CurPos >= Voice[VoiceNum].Sound^.SoundSize
  735.                   then
  736.                     begin
  737.                       DeallocateVoice(VoiceNum);
  738.                       Dec(VoiceCount);
  739.                     end;
  740.           end;
  741.       end;
  742.  
  743.  
  744.    {Utility functions}
  745.     procedure SetCurBlock(BlockNum: byte);
  746.       begin
  747.         CurBlock := BlockNum;
  748.         CurBlockPtr := pointer(BlockPtr[BlockNum]);
  749.       end;
  750.  
  751.     procedure ToggleBlock;
  752.       begin
  753.         if CurBlock = 1
  754.           then SetCurBlock(2)
  755.           else SetCurBlock(1);
  756.       end;
  757.  
  758.     procedure SilenceBlock;
  759.       begin
  760.         FillChar(MixingBlock, BlockLength*2, 0);  {FillChar uses REP STOSW}
  761.       end;
  762.  
  763.     function GetLinearAddr(Ptr: pointer): LongInt;
  764.       begin
  765.         GetLinearAddr := LongInt(Seg(Ptr^))*16 + LongInt(Ofs(Ptr^));
  766.       end;
  767.  
  768.     function NormalizePtr(p: pointer): pointer;
  769.       var
  770.         LinearAddr: LongInt;
  771.       begin
  772.         LinearAddr := GetLinearAddr(p);
  773.         NormalizePtr := Ptr(LinearAddr div 16, LinearAddr mod 16);
  774.       end;
  775.  
  776.  
  777.     procedure InitClip8;
  778.       var
  779.         i, Value: integer;
  780.       begin
  781.         for i := -128*Voices to 128*Voices do
  782.           begin
  783.             Value := i;
  784.             if (Value < -128) then Value := -128;
  785.             if (Value > +127) then Value := +127;
  786.  
  787.             Clip8[i] := Value + 128;
  788.           end;
  789.       end;
  790.  
  791.     procedure InitMixing;
  792.       var
  793.         i: integer;
  794.       begin
  795.         for i := 0 to Voices-1 do DeallocateVoice(i);
  796.         VoiceCount := 0;
  797.  
  798.         if SixteenBit
  799.           then
  800.             begin
  801.              {Find a block of memory that does not cross a page boundary}
  802.               GetMem(OutMemArea, 4*BufferLength);
  803.               if ((GetLinearAddr(OutMemArea) div 2) mod 65536)+BufferLength < 65536
  804.                 then Out16Buffer := OutMemArea
  805.                 else Out16Buffer := NormalizePtr(Ptr(Seg(OutMemArea^), Ofs(OutMemArea^)+2*BufferLength));
  806.               for i := 1 to 2 do
  807.                 BlockPtr[i] := NormalizePtr(Addr(Out16Buffer^[i]));
  808.              {DMA parameters}
  809.               BufferAddr := GetLinearAddr(pointer(Out16Buffer));
  810.               BufferPage := BufferAddr div 65536;
  811.               BufferOfs  := (BufferAddr div 2) mod 65536;
  812.               for i := 1 to 2 do
  813.                 BlockAddr[i] := GetLinearAddr(pointer(BlockPtr[i]));
  814.               for i := 1 to 2 do
  815.                 BlockPage[i] := BlockAddr[i] div 65536;
  816.               for i := 1 to 2 do
  817.                 BlockOfs[i]  := (BlockAddr[i] div 2) mod 65536;
  818.               FillChar(Out16Buffer^, BufferLength*2, $00);   {Signed   16-bit}
  819.             end
  820.           else
  821.             begin
  822.              {Find a block of memory that does not cross a page boundary}
  823.               GetMem(OutMemArea, 2*BufferLength);
  824.               if (GetLinearAddr(OutMemArea) mod 65536)+BufferLength < 65536
  825.                 then Out8Buffer := OutMemArea
  826.                 else Out8Buffer := NormalizePtr(Ptr(Seg(OutMemArea^), Ofs(OutMemArea^)+BufferLength));
  827.               for i := 1 to 2 do
  828.                 BlockPtr[i] := NormalizePtr(Addr(Out8Buffer^[i]));
  829.              {DMA parameters}
  830.               BufferAddr := GetLinearAddr(pointer(Out8Buffer));
  831.               BufferPage := BufferAddr div 65536;
  832.               BufferOfs  := BufferAddr mod 65536;
  833.               for i := 1 to 2 do
  834.                 BlockAddr[i] := GetLinearAddr(pointer(BlockPtr[i]));
  835.               for i := 1 to 2 do
  836.                 BlockPage[i] := BlockAddr[i] div 65536;
  837.               for i := 1 to 2 do
  838.                 BlockOfs[i]  := BlockAddr[i] mod 65536;
  839.               FillChar(Out8Buffer^, BufferLength, $80);      {Unsigned  8-bit}
  840.  
  841.               InitClip8;
  842.             end;
  843.  
  844.         FillChar(MixingBlock, BlockLength*2, $00);
  845.  
  846.         SetCurBlock(1);
  847.         IntCount := 0;
  848.         StartDAC;
  849.       end;
  850.  
  851.     procedure ShutdownMixing;
  852.       begin
  853.         StopDAC;
  854.  
  855.         if SixteenBit
  856.           then FreeMem(OutMemArea, 4*BufferLength)
  857.           else FreeMem(OutMemArea, 2*BufferLength);
  858.       end;
  859.  
  860.  
  861.  
  862.     procedure CopySound(Sound: PSound; var CurPos: LongInt; CopyLength: word; Loop: boolean);
  863.       var
  864.         SoundSize: LongInt;
  865.         DestPtr: pointer;
  866.         MoveParams: TMoveParams;
  867.       begin
  868.         SoundSize := Sound^.SoundSize;
  869.         DestPtr := pointer(@SoundBlock);
  870.         MoveParams.SourceHandle := Sound^.XMSHandle;
  871.         MoveParams.DestHandle   := 0;
  872.         while CopyLength > 0 do
  873.           begin
  874.            {Compute max transfer size}
  875.             if CopyLength < SoundSize-CurPos
  876.               then MoveParams.Length := CopyLength
  877.               else MoveParams.Length := SoundSize-CurPos;
  878.  
  879.            {Compute starting dest. offset and update offset for next block}
  880.             MoveParams.SourceOffset := Sound^.StartOfs + CurPos;
  881.             CurPos := CurPos + MoveParams.Length;
  882.             if Loop then CurPos := CurPos mod SoundSize;
  883.  
  884.            {Compute starting source offset and update offset for next block}
  885.             MoveParams.DestOffset := LongInt(DestPtr);
  886.             DestPtr := NormalizePtr(Ptr(Seg(DestPtr^), Ofs(DestPtr^)+MoveParams.Length));
  887.  
  888.            {Update remaining count for next iteration}
  889.             CopyLength := CopyLength - MoveParams.Length;
  890.  
  891.            {Move block}
  892.             MoveParams.Length := ((MoveParams.Length+1) div 2) * 2;
  893.               {XMS copy lengths must be a multiple of two}
  894.             XMSMove(@MoveParams);  {Luckily, the XMS driver is re-entrant}
  895.           end;
  896.       end;
  897.  
  898.     procedure MixVoice(VoiceNum: byte);
  899.       var
  900.         MixLength: word;
  901.       begin
  902.         with Voice[VoiceNum] do
  903.           if Loop
  904.             then
  905.               MixLength := BlockLength
  906.             else
  907.               if BlockLength < Sound^.SoundSize-CurPos
  908.                 then MixLength := BlockLength
  909.                 else MixLength := Sound^.SoundSize-CurPos;
  910.         CopySound(Voice[VoiceNum].Sound, Voice[VoiceNum].CurPos, MixLength, Voice[VoiceNum].Loop);
  911.         asm
  912.           lea  si, SoundBlock         {DS:SI -> Sound data (Source)          }
  913.           mov  ax, ds                 {ES:DI -> Mixing block (Destination)   }
  914.           mov  es, ax
  915.           lea  di, MixingBlock
  916.           mov  cx, MixLength          {CX = Number of samples to copy        }
  917.  
  918.          @MixSample:
  919.           mov  al, [si]               {Load a sample from the sound block    }
  920.           inc  si                     { increment pointer                    }
  921.           cbw                         {Convert it to a 16-bit signed sample  }
  922.           add  es:[di], ax            {Add it into the mixing buffer         }
  923.           add  di, 2                  {Next word in mixing buffer            }
  924.           dec  cx                     {Loop for next sample                  }
  925.           jnz  @MixSample
  926.         end;
  927.       end;
  928.  
  929.     procedure MixVoices;
  930.       var
  931.         i: word;
  932.       begin
  933.         SilenceBlock;
  934.         for i := 0 to Voices-1 do
  935.           if VoiceInUse[i]
  936.             then
  937.               MixVoice(i);
  938.       end;
  939.  
  940.     procedure CopyData16; assembler;
  941.       asm
  942.         lea   si, MixingBlock         {DS:SI -> 16-bit input block           }
  943.         les   di, [CurBlockPtr]       {ES:DI -> 16-bit output block          }
  944.         mov   cx, BlockLength         {CX = Number of samples to copy        }
  945.  
  946.        @CopySample:
  947.         mov   ax, [si]                {Load a sample from the mixing block   }
  948.         add   di, 2                   {Increment destination pointer         }
  949.         sal   ax, 5                   {Shift sample left to fill 16-bit range}
  950.         add   si, 2                   {Increment source pointer              }
  951.         mov   es:[di-2], ax           {Store sample in output block          }
  952.         dec   cx                      {Process the next sample               }
  953.         jnz   @CopySample
  954.       end;
  955.  
  956.     procedure CopyData8; assembler;
  957.       asm
  958.         push  bp
  959.         mov   dx, ss                  {Preserve SS in DX                     }
  960.         pushf
  961.         cli                           {Disable interrupts                    }
  962.         mov   ax, ds                  {Using SS for data                     }
  963.         mov   ss, ax
  964.  
  965.         lea   si, Clip8               {DS:SI -> 8-bit clipping buffer        }
  966.         add   si, 128*Voices          {DS:SI -> Center of clipping buffer    }
  967.  
  968.         lea   bp, MixingBlock         {SS:BP -> 16-bit input block           }
  969.         les   di, [CurBlockPtr]       {ES:DI -> 8-bit output block           }
  970.         mov   cx, BlockLength         {CX = Number of samples to copy        }
  971.  
  972.        @CopySample:
  973.         mov   bx, [bp]                {BX = Sample from mixing block         }
  974.         inc   di                      {Increment destination pointer (DI)    }
  975.         add   bp, 2                   {Increment source pointer (BP)         }
  976.         mov   al, [si+bx]             {AL = Clipped sample                   }
  977.         mov   es:[di-1], al           {Store sample in output block          }
  978.         dec   cx                      {Process the next sample               }
  979.         jnz   @CopySample
  980.  
  981.         mov   ss, dx                  {Restore SS                            }
  982.         popf                          {Restore flags                         }
  983.         pop   bp
  984.       end;
  985.  
  986.     procedure CopyData;
  987.       begin
  988.         if SixteenBit
  989.           then CopyData16
  990.           else CopyData8;
  991.       end;
  992.  
  993.     procedure StartBlock_SC; {Starts a single-cycle DMA transfer}
  994.       begin
  995.         Port[DMAMaskPort]     := DMAStopMask;
  996.         Port[DMAClrPtrPort]   := $00;
  997.         Port[DMAModePort]     := DMAMode;
  998.         Port[DMABaseAddrPort] := Lo(BlockOfs[CurBlock]);
  999.         Port[DMABaseAddrPort] := Hi(BlockOfs[CurBlock]);
  1000.         Port[DMACountPort]    := Lo(DMALength-1);
  1001.         Port[DMACountPort]    := Hi(DMALength-1);
  1002.         Port[DMAPagePort]     := BlockPage[CurBlock];
  1003.         Port[DMAMaskPort]     := DMAStartMask;
  1004.         WriteDSP($14);                {8-bit single-cycle DMA sound output   }
  1005.         WriteDSP(Lo(BlockLength - 1));
  1006.         WriteDSP(Hi(BlockLength - 1));
  1007.       end;
  1008.  
  1009. {$IFNDEF VER60}
  1010.     var Save_Test8086: byte; {CPU type flag}
  1011. {$ENDIF}
  1012.  
  1013.     procedure IntHandler; interrupt;
  1014.       var
  1015.         Temp: byte;
  1016.       begin
  1017.        {On a 386 or higher, Turbo Pascal 7 uses 32-bit registers for LongInt }
  1018.        {math.  Unfortunately, it doesn't preserve these registers when       }
  1019.        {generating code to handle interrupts, so they are occasionally       }
  1020.        {corrupted.  This can cause a problem with LongInt math in your       }
  1021.        {program or in TSRs. The below code disables 32-bit instructions for  }
  1022.        {the interrupt to prevent 32-bit register corruption.                 }
  1023. {$IFNDEF VER60}
  1024.         Save_Test8086 := Test8086;
  1025.         Test8086 := 0;
  1026. {$ENDIF}
  1027.  
  1028.         Inc(IntCount);
  1029.  
  1030.         if not(AutoInit) {Start next block first if not using auto-init DMA}
  1031.           then
  1032.             begin
  1033.               StartBlock_SC;
  1034.               CopyData;
  1035.               ToggleBlock;
  1036.             end;
  1037.  
  1038.         UpdateVoices;
  1039.         MixVoices;
  1040.  
  1041.         if (AutoInit)
  1042.           then
  1043.             begin
  1044.               CopyData;
  1045.               ToggleBlock;
  1046.             end;
  1047.  
  1048. {$IFNDEF VER60}
  1049.         Test8086 := Save_Test8086;
  1050. {$ENDIF}
  1051.  
  1052.         Temp := Port[AckPort];
  1053.         Port[$A0] := $20;
  1054.         Port[$20] := $20;
  1055.       end;
  1056.  
  1057.     procedure EnableInterrupts;  InLine($FB); {STI}
  1058.     procedure DisableInterrupts; InLine($FA); {CLI}
  1059.  
  1060.     procedure InstallHandler;
  1061.       begin
  1062.         DisableInterrupts;
  1063.         Port[PICMaskPort] := Port[PICMaskPort] or IRQStopMask;
  1064.         GetIntVec(IRQIntVector, OldIntVector);
  1065.         SetIntVec(IRQIntVector, @IntHandler);
  1066.         Port[PICMaskPort] := Port[PICMaskPort] and IRQStartMask;
  1067.         EnableInterrupts;
  1068.         HandlerInstalled := true;
  1069.       end;
  1070.  
  1071.     procedure UninstallHandler;
  1072.       begin
  1073.         DisableInterrupts;
  1074.         Port[PICMaskPort] := Port[PICMaskPort] or IRQStopMask;
  1075.         SetIntVec(IRQIntVector, OldIntVector);
  1076.         EnableInterrupts;
  1077.         HandlerInstalled := false;
  1078.       end;
  1079.  
  1080.     procedure MixExitProc;       {Called automatically on program termination}
  1081.       begin
  1082.         ExitProc := OldExitProc;
  1083.  
  1084.         if (SMIXinitialized)
  1085.           then
  1086.             begin
  1087.               StopDAC;
  1088.               ShutdownSB;
  1089.             end;
  1090.       end;
  1091.  
  1092.   begin
  1093.     SMIXinitialized      := false;
  1094.     HandlerInstalled     := false;
  1095.     SharedEMB            := false;
  1096.     ResourceFile         := false;
  1097.  
  1098.     OldExitProc := ExitProc;
  1099.     ExitProc    := @MixExitProc;
  1100.   end.
  1101.